// Copyright (c) Microsoft Corporation. 
// Licensed under the MIT license.

#include "SimplygonEditorModule.h"
#include "SimplygonPluginMeshReductionModule.h"
#include "SimplygonEditorCommands.h"
#include "LevelEditor.h"
#include "SimplygonModule.h"
#include "AssetToolsModule.h"
#include "Misc/MessageDialog.h"
#include "SimplygonStyle.h"
#include "Editor.h"
#include "Framework/MultiBox/MultiBoxBuilder.h"
#include "IMeshReductionManagerModule.h"
#include "CoreGlobals.h"
#include "Misc/ConfigCacheIni.h"
#include "Misc/MessageDialog.h"
#include "NavigationSystem.h"
#include "SimplygonUE4UI.h"
#include "SimplygonPropertyCasters.h"
#include "Engine/StaticMeshActor.h"

#ifdef IMPOSTER_BAKER
#include "ImpostorGeneratorHelperActor.h"
#endif

//Customizations
#include "Customization/TelemetryCustomizations.h"
#include "Customization/ReductionPipelineCustomizations.h"
#include "Customization/AggregationPipelineCustomizations.h"
#include "Customization/RemeshingPipelineCustomizations.h"
#include "Customization/ReductionSettingsCustomizations.h"
#include "Customization/ColorCasterSettingsCustomizations.h"
#include "Customization/NormalCasterSettingsCustomizations.h"
#include "Customization/AOCasterSettingsCustomizations.h"
#include "Customization/DisplacementCasterSettingsCustomizations.h"
#include "Customization/GeometryDataCasterSettingsCustomizations.h"
#include "Customization/OpacityCasterSettingsCustomizations.h"
#include "Customization/VertexColorCasterSettingsCustomizations.h"
#include "Customization/NavSysVisibilityDataVisualizerCustomizations.h"
#include "Customization/ClippingPlaneDataVisualizerCustomizations.h"
#include "Customization/ImpostorGeneratorHelperActorCustomizations.h"
#include "Customization/MappingImageSettingsCustomizations.h"

#ifdef IMPOSTER_BAKER
#include "Customization/ImpostorFromSingleViewCustomizations.h"
#endif

#include "ISimplygonPluginUtilities.h"
#include "ISimplygonPluginUtilitiesModule.h"
#include "NavSysVisibilityDataVisualizer.h"
#include "ClippingPlaneDataVisualizer.h"
#include "NavMesh/RecastNavMesh.h"
#include "Telemetry.h"
#include "TelemetryManager.h"

#include "MeshMergeUtilities/Private/MeshMergeHelpers.h"
#include "SimplygonModule.h"
#include "ISimplygonPluginUtilitiesModule.h"
#include "ISimplygonPluginUtilities.h"
#include "MaterialUtilities.h"
#include "Developer/MeshMergeUtilities/Private/ProxyMaterialUtilities.h"
#include "SimplygonPluginUtilities/Private/SimplygonProcessor.h"
#include "SimplygonDataConverter.h"
#include "ScopedTransaction.h"
#include "Misc/ScopedSlowTask.h"
#include "ISettingsModule.h"
#include "Interfaces/IPluginManager.h"


DEFINE_LOG_CATEGORY_STATIC(LogSimplygonEditorModule, Verbose, All);
#define LOCTEXT_NAMESPACE "SimplygonSDKEditorModule"

class FSimplygonEditorModule : public ISimplygonEditorModule
{
public:
	/** IModuleInterface implementation */
	virtual void StartupModule() override;
	virtual void ShutdownModule() override;
	virtual void GetRandomSamplesFromNavigableSpace(const UWorld* InWolrd, const int NumSamples, const float MinZ, const float MaxZ, TArray<FVector>& OutSamples) override;
#ifdef IMPOSTER_BAKER
	virtual void BakeImpostors(AImpostorGeneratorHelperActor* InActor = nullptr) override;
#endif
	

private:
	void AddToolbarExtension(FToolBarBuilder& Builder);
	void AddMenuExtension(FMenuBuilder& Builder);
	bool IsSimplygonAvailable();
	bool AreAllInterfacesUsingSimplygon();
	void ToggleSimplygon();
	TSharedRef<SWidget> FillToolbarMenu();
	void ResetMeshReductionModules(bool bUpdateIni = true);
	void SetSimplygonAsMeshReductionModule(bool UpdateIni = true);
	void GetReductionModulesNotUsingSimplygon(TArray<FString>& OutNames);
	void InitConsoleVars();
	void InitCommands();
	void InitPluginVersion();
	
	void AddClippingPlanesVisualizer();
	void AddNavMeshSamplerAndVisualizer();
	
	void OnOpenPluginSettings();

	//void BakeImpostors();
#ifdef IMPOSTER_BAKER
	void CompletedImposterBake(const FSimplygonJobData& InJobData, Simplygon::spPipeline InPipeline);
#endif

private:
	TSharedPtr<FExtender> ToolbarExtender;
	TSharedPtr<class FUICommandList> SimplygonEditorCommands;
	bool bSimplygonInitialized { false };
	FString PluginVersion;
	FSlateIcon SimplygonDisabledIcon;
	FSlateIcon SimplygonEnabledIcon;
	FSlateIcon SimplygonWarnIcon;
	FDelegateHandle MapChanged;

	static IConsoleVariable* CVarMeshReduction;
	static IConsoleVariable* CVarSkeletalMeshReduction;
	static IConsoleVariable* CVarProxyReduction;

	const FString UEDefaultMeshReduction = TEXT("QuadricMeshReduction");
	const FString UEDefaultSkelMeshReduction = TEXT("SkeletalMeshReduction");
	const FString UEDefaultProxyMeshReduction = TEXT("ProxyLODMeshReduction");
	const FString SimplygonReductionModuleName = TEXT("SimplygonPluginMeshReduction");

	void ResetIniConfig(bool bUseSimplygon, FString Ini);

	void HandleMapChanged(UWorld* InWorld, EMapChangeType InMapChangeType);
};

IMPLEMENT_MODULE(FSimplygonEditorModule, SimplygonEditor)

IConsoleVariable* FSimplygonEditorModule::CVarMeshReduction = nullptr;
IConsoleVariable* FSimplygonEditorModule::CVarSkeletalMeshReduction = nullptr;
IConsoleVariable* FSimplygonEditorModule::CVarProxyReduction = nullptr;


void FSimplygonEditorModule::ResetIniConfig(bool bUseSimplygon, FString Ini)
{
	
	FString MeshReductionModuleName;
	GConfig->GetString(TEXT("/Script/Engine.MeshSimplificationSettings"), TEXT("r.MeshReductionModule"), MeshReductionModuleName, Ini);
	GConfig->SetString(TEXT("/Script/Engine.MeshSimplificationSettings"), TEXT("r.MeshReductionModule"), bUseSimplygon ? *SimplygonReductionModuleName : *UEDefaultMeshReduction, Ini);


	FString SkeletalMeshReductionModuleName;
	GConfig->GetString(TEXT("/Script/Engine.SkeletalMeshSimplificationSettings"), TEXT("r.SkeletalMeshReductionModule"), SkeletalMeshReductionModuleName, Ini);
	GConfig->SetString(TEXT("/Script/Engine.SkeletalMeshSimplificationSettings"), TEXT("r.SkeletalMeshReductionModule"), bUseSimplygon ? *SimplygonReductionModuleName : *UEDefaultSkelMeshReduction, Ini);


	FString ProxyMeshReductionModuleName;
	GConfig->GetString(TEXT("/Script/Engine.ProxyLODMeshSimplificationSettings"), TEXT("r.ProxyLODMeshReductionModule"), ProxyMeshReductionModuleName, Ini);
	GConfig->SetString(TEXT("/Script/Engine.ProxyLODMeshSimplificationSettings"), TEXT("r.ProxyLODMeshReductionModule"), bUseSimplygon ? *SimplygonReductionModuleName : *UEDefaultProxyMeshReduction, Ini);
	
	
}
void FSimplygonEditorModule::InitConsoleVars()
{
	CVarMeshReduction = IConsoleManager::Get().FindConsoleVariable(TEXT("r.MeshReductionModule"));
	CVarSkeletalMeshReduction = IConsoleManager::Get().FindConsoleVariable(TEXT("r.SkeletalMeshReductionModule"));
	CVarProxyReduction = IConsoleManager::Get().FindConsoleVariable(TEXT("r.ProxyLODMeshReductionModule"));
}

void FSimplygonEditorModule::InitCommands()
{
	FSimplygonEditorCommands::Register();
	SimplygonEditorCommands = MakeShareable(new FUICommandList);
	
	// Map Actions
	if (!SimplygonEditorCommands->IsActionMapped(FSimplygonEditorCommands::Get().EnableSimplygonMeshReduction))
	{
		SimplygonEditorCommands->MapAction(
			FSimplygonEditorCommands::Get().EnableSimplygonMeshReduction,
			FExecuteAction::CreateRaw(this, &FSimplygonEditorModule::ToggleSimplygon),
			FCanExecuteAction(),
			FIsActionChecked::CreateRaw(this, &FSimplygonEditorModule::AreAllInterfacesUsingSimplygon));
	}

	if (!SimplygonEditorCommands->IsActionMapped(FSimplygonEditorCommands::Get().AddClippingPlanesVisualizer))
	{
		SimplygonEditorCommands->MapAction(
			FSimplygonEditorCommands::Get().AddClippingPlanesVisualizer,
			FExecuteAction::CreateRaw(this, &FSimplygonEditorModule::AddClippingPlanesVisualizer),
			FCanExecuteAction(),
			FIsActionChecked::CreateRaw(this, &FSimplygonEditorModule::AreAllInterfacesUsingSimplygon));
	}

	if (!SimplygonEditorCommands->IsActionMapped(FSimplygonEditorCommands::Get().AddNavMeshSamplesVisualizer))
	{
		SimplygonEditorCommands->MapAction(
			FSimplygonEditorCommands::Get().AddNavMeshSamplesVisualizer,
			FExecuteAction::CreateRaw(this, &FSimplygonEditorModule::AddNavMeshSamplerAndVisualizer),
			FCanExecuteAction(),
			FIsActionChecked::CreateRaw(this, &FSimplygonEditorModule::AreAllInterfacesUsingSimplygon));
	}

#ifdef IMPOSTER_BAKER
		AImpostorGeneratorHelperActor* DummpyActor = nullptr;
		SimplygonEditorCommands->MapAction(
			FSimplygonEditorCommands::Get().BakeImpostors,
			FExecuteAction::CreateRaw(this, &FSimplygonEditorModule::BakeImpostors, DummpyActor),
			FCanExecuteAction(),
			FIsActionChecked::CreateRaw(this, &FSimplygonEditorModule::AreAllInterfacesUsingSimplygon));
#endif
	if (!SimplygonEditorCommands->IsActionMapped(FSimplygonEditorCommands::Get().OpenPluginSettings))
	{
		SimplygonEditorCommands->MapAction(
			FSimplygonEditorCommands::Get().OpenPluginSettings,
			FExecuteAction::CreateRaw(this, &FSimplygonEditorModule::OnOpenPluginSettings),
			FCanExecuteAction(),
			FIsActionChecked::CreateRaw(this, &FSimplygonEditorModule::AreAllInterfacesUsingSimplygon));
	}
#ifdef DUMP_RPMALLOC_STATS
	if (!SimplygonEditorCommands->IsActionMapped(FSimplygonEditorCommands::Get().DumpMemoryStats))
	{
		SimplygonEditorCommands->MapAction(
			FSimplygonEditorCommands::Get().DumpMemoryStats,
			FExecuteAction::CreateLambda([ this]()
				{
					ISimplygonModule& Simplygon = FModuleManager::GetModuleChecked<ISimplygonModule>("Simplygon");

					if (Simplygon.GetSDK())
					{
						Simplygon.DumpMemoryStatistics();
					}
				}),
			FCanExecuteAction::CreateLambda([this]()->bool{
					FString SimplygonFlags = FPlatformMisc::GetEnvironmentVariable(TEXT("SIMPLYGON_9_FLAGS"));
					
					if (!SimplygonFlags.IsEmpty())
					{
						FString Left,Right;
						FString CurrentFlag = SimplygonFlags;
						while (CurrentFlag.Split(";", &Left, &Right))
						{
							if (Left.StartsWith("MemoryTrakcer") && CurrentFlag.Split("=", &Left, &Right))
							{
								if (!Right.IsEmpty())
								{
									return true;
								}
							}
							CurrentFlag = Right;
						}

						if (CurrentFlag.StartsWith("MemoryTracker") && CurrentFlag.Split("=", &Left, &Right))
						{
							if (!Right.IsEmpty())
							{
								return true;
							}
						}
						
					}

					return false;
				}),
			FIsActionChecked::CreateRaw(this, &FSimplygonEditorModule::AreAllInterfacesUsingSimplygon));
	}
#endif
}

void FSimplygonEditorModule::InitPluginVersion()
{
	FString PluginName = TEXT("Simplygon");
 
	IPluginManager& PluginManager = IPluginManager::Get();
	TSharedPtr<IPlugin> Plugin = PluginManager.FindPlugin(PluginName);
	if (Plugin.IsValid() && Plugin.Get()->GetName() == PluginName) 
	{
		const FPluginDescriptor& Descriptor = Plugin->GetDescriptor();
		PluginVersion = Descriptor.VersionName;
	}
}

void FSimplygonEditorModule::ResetMeshReductionModules(bool bUpdateIni)
{
	if(CVarMeshReduction->GetString().StartsWith(TEXT("Simplygon")))
		CVarMeshReduction->Set(*UEDefaultMeshReduction);
	if (CVarSkeletalMeshReduction->GetString().StartsWith(TEXT("Simplygon")))
		CVarSkeletalMeshReduction->Set(*UEDefaultSkelMeshReduction);
	if (CVarProxyReduction->GetString().StartsWith(TEXT("Simplygon")))
		CVarProxyReduction->Set(*UEDefaultProxyMeshReduction);

	if (bUpdateIni)
	{
		ResetIniConfig(false, GEngineIni);
		///ResetIniConfig(false, GEditorPerProjectIni);
	}

}

void FSimplygonEditorModule::SetSimplygonAsMeshReductionModule(bool bUpdateIni)
{
	
	CVarMeshReduction->Set(*SimplygonReductionModuleName);
	CVarSkeletalMeshReduction->Set(*SimplygonReductionModuleName);
	CVarProxyReduction->Set(*SimplygonReductionModuleName);

	if (bUpdateIni)
	{
		ResetIniConfig(true, GEngineIni);
		//ResetIniConfig(true, GEditorPerProjectIni);

	}
	
}


void FSimplygonEditorModule::ToggleSimplygon()
{
	
	const EAppReturnType::Type Result = FMessageDialog::Open(EAppMsgType::YesNo,  NSLOCTEXT("Message", "TootleSimplygonDialog", "This operation will restart the editor and update PerProjectConfig. Do you want to continue?"));

	if (Result == EAppReturnType::Yes)
	{
		if (IsSimplygonAvailable() && AreAllInterfacesUsingSimplygon())
		{
			ResetMeshReductionModules();
		}
		else
		{
			SetSimplygonAsMeshReductionModule();
		}
		FUnrealEdMisc::Get().RestartEditor(false);
	}
	
}
bool FSimplygonEditorModule::IsSimplygonAvailable()
{
	
	if (!FModuleManager::Get().IsModuleLoaded("Simplygon") || !FModuleManager::Get().IsModuleLoaded("SimplygonPluginMeshReduction"))
	{
		return false;
	}

	ISimplygonModule& Simplygon = FModuleManager::GetModuleChecked<ISimplygonModule>("Simplygon");

	if (!Simplygon.GetSDK())
	{
		return false;
	}

	ISimplygonPluginMeshReductionModule& SimplygonModule = FModuleManager::GetModuleChecked<ISimplygonPluginMeshReductionModule>("SimplygonPluginMeshReduction");

	// Check if we have Simplygon interfaces
	if (SimplygonModule.GetMeshMergingInterface() && SimplygonModule.GetStaticMeshReductionInterface() && SimplygonModule.GetSkeletalMeshReductionInterface())
	{
		return true;
	}

	return false;
}

bool FSimplygonEditorModule::AreAllInterfacesUsingSimplygon()
{
	if (!IsSimplygonAvailable())
		return false;

	const FString MeshReductionModuleName = CVarMeshReduction->GetString();
	const FString SkeletalMeshReductionModuleName = CVarSkeletalMeshReduction->GetString();
	const FString ProxyLODReductionModuleName = CVarProxyReduction->GetString();

	return MeshReductionModuleName.StartsWith("Simplygon") && SkeletalMeshReductionModuleName.StartsWith("Simplygon") && ProxyLODReductionModuleName.StartsWith("Simplygon");
}

void FSimplygonEditorModule::GetReductionModulesNotUsingSimplygon(TArray<FString>& OutNames)
{
	if (!IsSimplygonAvailable())
		return;

	TArray<FString> ModuleNamesToCheck = {TEXT("r.MeshReductionModule"),TEXT("r.SkeletalMeshReductionModule"),TEXT("r.ProxyLODMeshReductionModule") };

	for (auto Name : ModuleNamesToCheck)
	{
		const FString ModuleName = IConsoleManager::Get().FindConsoleVariable(*Name)->GetString();
		if (!ModuleName.StartsWith("Simplygon"))
		{
			Name.RemoveFromStart("r.");
			OutNames.Add(Name);
		}
	}
	
}

TSharedRef<SWidget> FSimplygonEditorModule::FillToolbarMenu()
{
	TSharedPtr<FExtender> MenuExtender = MakeShareable(new FExtender);
	

	FMenuBuilder MenuBuilder(true, SimplygonEditorCommands, MenuExtender);
		
	AddMenuExtension(MenuBuilder);

	return MenuBuilder.MakeWidget();
}

void FSimplygonEditorModule::AddToolbarExtension(FToolBarBuilder& Builder)
{
	Builder.AddComboButton(FUIAction(),
		FOnGetContent::CreateRaw(this, &FSimplygonEditorModule::FillToolbarMenu),
		LOCTEXT("SimplygonCombo_Label", "Simplygon"),
		TAttribute<FText>::Create([this]() 
			{
				FString TooltipMessage = TEXT("Simplygon is availble and used for [MeshReduction,SkeletalMeshReduction,ProxyLODs]");

				if (!IsSimplygonAvailable())
					TooltipMessage = TEXT("Simplygon not availble.");

				if (!AreAllInterfacesUsingSimplygon())
				{
					TArray<FString> ModuleNames;
					GetReductionModulesNotUsingSimplygon(ModuleNames);
					
					TooltipMessage = FString::Printf(TEXT("Modules not using Simplygon [%s]"),*FString::Join(ModuleNames, TEXT(",")));
				}
				return FText::Format(LOCTEXT("SimplygonComboToolTip", "'{0}'"), FText::FromString(TooltipMessage));
			}),
		TAttribute<FSlateIcon>::Create([this]() 
			{ 
				if (!IsSimplygonAvailable())
					return SimplygonDisabledIcon;

				if (AreAllInterfacesUsingSimplygon())
					return SimplygonEnabledIcon;
				else
					return SimplygonWarnIcon;
			}),
		false);
}

void FSimplygonEditorModule::AddMenuExtension(FMenuBuilder& Builder)
{
	Builder.BeginSection(TEXT("General"), LOCTEXT("SimplygonGeneralSectionHeader", "General"));
	Builder.AddMenuEntry(FSimplygonEditorCommands::Get().EnableSimplygonMeshReduction);
	Builder.AddMenuEntry(FSimplygonEditorCommands::Get().OpenPluginSettings);
	//Builder.AddMenuEntry(FSimplygonEditorCommands::Get().ShowPluginVersion, NAME_None, FText::FromString(TEXT("Version: ") + PluginVersion));
	Builder.EndSection();

	Builder.BeginSection(TEXT("Simplygon Visualizers"), LOCTEXT("SimplygonVisualizersSectionHeader", "Simplygon Visualizers"));
	Builder.AddMenuEntry(FSimplygonEditorCommands::Get().AddClippingPlanesVisualizer);
	Builder.AddMenuEntry(FSimplygonEditorCommands::Get().AddNavMeshSamplesVisualizer);
	Builder.EndSection();

#ifdef IMPOSTER_BAKER
	Builder.BeginSection(TEXT("Experimental"), LOCTEXT("SimplygonExperimentalSectionHeader", "Experimental"));
	Builder.AddMenuEntry(FSimplygonEditorCommands::Get().BakeImpostors);
	Builder.EndSection();
#endif
#ifdef DUMP_RPMALLOC_STATS
	Builder.BeginSection(TEXT("Debug"), LOCTEXT("SimplygonDebugHeader", "Debug"));
	Builder.AddMenuEntry(FSimplygonEditorCommands::Get().DumpMemoryStats);
	Builder.EndSection();
#endif
	// NOTE: This should always be the last entry in the menu
	Builder.BeginSection(TEXT("Simplygon Version"), FText::FromString(TEXT("Version: ") + PluginVersion));
	Builder.AddSeparator();
	Builder.EndSection();
}

void FSimplygonEditorModule::StartupModule()
{
	SimplygonDisabledIcon = FSlateIcon(FSimplygonStyle::GetStyleSetName(), "SimplygonIcon.Disabled");
	SimplygonEnabledIcon = FSlateIcon(FSimplygonStyle::GetStyleSetName(), "SimplygonIcon.Enabled");
	SimplygonWarnIcon = FSlateIcon(FSimplygonStyle::GetStyleSetName(), "SimplygonIcon.Warn");

	// Initilize and cache plugin version for later use
	InitPluginVersion();

	//Static console vars
	InitConsoleVars();

	bSimplygonInitialized = IsSimplygonAvailable();

	InitCommands();

	// Add Simplygon button to Level Editor Toolbar
	FLevelEditorModule& LevelEditorModule = FModuleManager::LoadModuleChecked<FLevelEditorModule>("LevelEditor");
	{
		auto NoCmdList = MakeShareable(new FUICommandList);
		ToolbarExtender = MakeShareable(new FExtender);
		ToolbarExtender->AddToolBarExtension("Compile", EExtensionHook::After,
			NoCmdList,
			FToolBarExtensionDelegate::CreateRaw(this, &FSimplygonEditorModule::AddToolbarExtension));
		
		LevelEditorModule.GetToolBarExtensibilityManager()->AddExtender(ToolbarExtender);
	}

	MapChanged = LevelEditorModule.OnMapChanged().AddRaw(this,&FSimplygonEditorModule::HandleMapChanged);

	//Register Customizations
	FPropertyEditorModule& PropertyModule = FModuleManager::LoadModuleChecked<FPropertyEditorModule>("PropertyEditor");

#ifdef IMPOSTER_BAKER
	PropertyModule.RegisterCustomClassLayout(FImpostorFromSingleViewPipelineSettings::StaticStruct()->GetFName(), FOnGetDetailCustomizationInstance::CreateStatic(&FImpostorFromSingleViewCustomization::MakeInstance));
#endif

	PropertyModule.RegisterCustomPropertyTypeLayout(FReductionPipelineSettings::StaticStruct()->GetFName(), FOnGetPropertyTypeCustomizationInstance::CreateStatic(&FReductionPipelinePropertyCustomization::MakeInstance));
	PropertyModule.RegisterCustomPropertyTypeLayout(FReductionSettings::StaticStruct()->GetFName(), FOnGetPropertyTypeCustomizationInstance::CreateStatic(&FReductionSettingsPropertyCustomization::MakeInstance));
	PropertyModule.RegisterCustomPropertyTypeLayout(FAggregationPipelineSettings::StaticStruct()->GetFName(), FOnGetPropertyTypeCustomizationInstance::CreateStatic(&FAggregationPipelinePropertyCustomization::MakeInstance));
	PropertyModule.RegisterCustomPropertyTypeLayout(FRemeshingPipelineSettings::StaticStruct()->GetFName(), FOnGetPropertyTypeCustomizationInstance::CreateStatic(&FRemeshingPipelinePropertyCustomization::MakeInstance));

	PropertyModule.RegisterCustomPropertyTypeLayout(FColorCasterSettings::StaticStruct()->GetFName(), FOnGetPropertyTypeCustomizationInstance::CreateStatic(&FColorCasterSettingsPropertyCustomization::MakeInstance));
	PropertyModule.RegisterCustomPropertyTypeLayout(FNormalCasterSettings::StaticStruct()->GetFName(), FOnGetPropertyTypeCustomizationInstance::CreateStatic(&FNormalCasterSettingsPropertyCustomization::MakeInstance));
	PropertyModule.RegisterCustomPropertyTypeLayout(FAmbientOcclusionCasterSettings::StaticStruct()->GetFName(), FOnGetPropertyTypeCustomizationInstance::CreateStatic(&FAOCasterSettingsPropertyCustomization::MakeInstance));
	PropertyModule.RegisterCustomPropertyTypeLayout(FDisplacementCasterSettings::StaticStruct()->GetFName(), FOnGetPropertyTypeCustomizationInstance::CreateStatic(&FDisplacementCasterSettingsPropertyCustomization::MakeInstance));
	PropertyModule.RegisterCustomPropertyTypeLayout(FGeometryDataCasterSettings::StaticStruct()->GetFName(), FOnGetPropertyTypeCustomizationInstance::CreateStatic(&FGeometryDataCasterSettingsPropertyCustomization::MakeInstance));
	PropertyModule.RegisterCustomPropertyTypeLayout(FOpacityCasterSettings::StaticStruct()->GetFName(), FOnGetPropertyTypeCustomizationInstance::CreateStatic(&FOpacityCasterSettingsPropertyCustomization::MakeInstance));
	PropertyModule.RegisterCustomPropertyTypeLayout(FVertexColorCasterSettings::StaticStruct()->GetFName(), FOnGetPropertyTypeCustomizationInstance::CreateStatic(&FVertexColorCasterSettingsPropertyCustomization::MakeInstance));
#ifdef IMPOSTER_BAKER
	PropertyModule.RegisterCustomPropertyTypeLayout(FImpostorFromSingleViewPipelineSettings::StaticStruct()->GetFName(), FOnGetPropertyTypeCustomizationInstance::CreateStatic(&FImpostorFromSingleViewPropertyCustomization::MakeInstance));
#endif

	PropertyModule.RegisterCustomPropertyTypeLayout(
	    FMappingImageSettings::StaticStruct()->GetFName(),
	    FOnGetPropertyTypeCustomizationInstance::CreateStatic( &FMappingImageSettingsPropertyCustomization::MakeInstance ) );

	PropertyModule.RegisterCustomClassLayout("SimplygonVisibilitySamplerVisualizer", FOnGetDetailCustomizationInstance::CreateStatic(&FSimplygonVisualizerDetailsCustomization::MakeInstance));
	PropertyModule.RegisterCustomClassLayout("SimplygonClippingPlanesVisualizer", FOnGetDetailCustomizationInstance::CreateStatic(&FSimplygonClippingPlaneVisualizerDetailsCustomization::MakeInstance));
	

	PropertyModule.RegisterCustomClassLayout("SimplygonSettings", FOnGetDetailCustomizationInstance::CreateStatic(&FSimplygonSettingCustomization::MakeInstance));
#ifdef IMPOSTER_BAKER
	PropertyModule.RegisterCustomClassLayout("ImpostorGeneratorHelperActor", FOnGetDetailCustomizationInstance::CreateStatic(&FImpostorGeneratorHelperActorCustomization::MakeInstance));
#endif
	
}

void FSimplygonEditorModule::ShutdownModule()
{
	if (FModuleManager::Get().IsModuleLoaded("LevelEditor"))
	{
		FLevelEditorModule& LevelEditorModule = FModuleManager::LoadModuleChecked<FLevelEditorModule>("LevelEditor");
		LevelEditorModule.GetToolBarExtensibilityManager()->RemoveExtender(ToolbarExtender);
		LevelEditorModule.OnMapChanged().Remove(MapChanged);
	}
	
}

void FSimplygonEditorModule::GetRandomSamplesFromNavigableSpace(const UWorld* InWolrd, const int NumSamples, const float MinZ, const float MaxZ, TArray<FVector>& OutSamples)
{

	if (!InWolrd)
		return;

	const UNavigationSystemV1* NavSystem = FNavigationSystem::GetCurrent<const UNavigationSystemV1>(InWolrd);
		

	FNavLocation OutLocation;
	const FBoxSphereBounds InEstimatedBounds;

	TSet<FNavigationBounds> NavigableBounds = NavSystem->GetNavigationBounds();
	TArray<FNavigationBounds> NavBounds = NavigableBounds.Array();


	if (NavBounds.Num() > 0)
	{
		bool IsInsideNavigableArea = false;

		// Check if the proxy that is being generated is outside the navigable area. 
		// This check is to speed up bulk processing so that only irrelevant meshes use visibility
		for (int32 NavBoundIndex = 0; NavBoundIndex < NavBounds.Num(); ++NavBoundIndex)
		{
			if (InEstimatedBounds.GetBox().IsInside(NavBounds[NavBoundIndex].AreaBox))
			{
				IsInsideNavigableArea = true;
				break;
			}

		}

		TArray<float> NavBoundsArea;
		float TotalArea = 0.0f;

		for (auto& Bound : NavBounds)
		{
			float Length;
			FVector Dir;
			Bound.AreaBox.GetExtent().ToDirectionAndLength(Dir, Length);
			FBox2D Bounds2D(FVector2D(Bound.AreaBox.Min.X, Bound.AreaBox.Min.Y), FVector2D(Bound.AreaBox.Max.X, Bound.AreaBox.Max.Y));
			float Area = float(Bounds2D.GetArea());
			NavBoundsArea.Add(Area);
			TotalArea += Area;
		}

		// scale by area of nav bounds
		//trivial implementation sample points are generated in all nav bounds.
		// alternate would be to have same number of sample points per bound area. 
		// or take in object center and expand radius from there and if object is in nav bound. 
		if (!IsInsideNavigableArea)
		{
			
			for (int32 NavBoundIndex = 0; NavBoundIndex < NavBounds.Num(); ++NavBoundIndex)
			{
				int32 NumOfSamplePerNavBound = int32(NumSamples * ((NavBoundsArea[NavBoundIndex] / TotalArea)));
				int32 PointsGeneratedForArea = 0;
				while (PointsGeneratedForArea < NumOfSamplePerNavBound)
				{
					float Length;
					FVector Direction;

					NavBounds[NavBoundIndex].AreaBox.GetSize().ToDirectionAndLength(Direction, Length);

					
					if (NavSystem->GetRandomReachablePointInRadius(NavBounds[NavBoundIndex].AreaBox.GetCenter(), Length / 2, OutLocation))
					{
						FVector MinPos(OutLocation.Location.X, OutLocation.Location.Y, OutLocation.Location.Z + MinZ);
						FVector MaxPos(OutLocation.Location.X, OutLocation.Location.Y, OutLocation.Location.Z + MaxZ);

						OutSamples.Add(MinPos);
						OutSamples.Add(MaxPos);
						PointsGeneratedForArea += 2;

					}

				}
			}


			//take the debug geomtry and append to the samples
			auto NavMesh = Cast<ARecastNavMesh>(NavSystem->MainNavData);
			FRecastDebugGeometry DebugGeometry;
			NavMesh->GetDebugGeometry(DebugGeometry);
			for (auto& Vert : DebugGeometry.MeshVerts)
			{
				FVector MinPos(Vert.X, Vert.Y, Vert.Z + MinZ);
				FVector MaxPos(Vert.X, Vert.Y, Vert.Z + MaxZ);
				OutSamples.Add(MinPos);
				OutSamples.Add(MaxPos);
			}

		}
	}
}

template<class ActorType>
static ActorType* FindFirstOrDefaultActorOfTypeInLevel()
{
	ActorType* Result = nullptr;
	for (auto Level : GWorld->GetLevels())
	{
		if (Level->IsValidLowLevelFast() && Level->bIsVisible)
		{

			for (auto ActorIt = Level->Actors.CreateIterator(); ActorIt; ++ActorIt)
			{
				AActor* Actor = *ActorIt;
				if (Actor->IsValidLowLevelFast() && Actor->IsA(ActorType::StaticClass()))
				{
					Result = CastChecked<ActorType>(Actor);
					break;
				}
			}
		}
	}

	//UE_LOG(LogStandinUtilitiesModule, Warning, TEXT("FindActorsOfTypeInLevel No Actors Of type %s found."), *ActorType::StaticClass()->GetName());
	
	return Result;
}

void FSimplygonEditorModule::AddNavMeshSamplerAndVisualizer()
{
	ASimplygonVisibilitySamplerVisualizer* Actor = FindFirstOrDefaultActorOfTypeInLevel<ASimplygonVisibilitySamplerVisualizer>();
	FActorSpawnParameters Params;
	Params.Name = TEXT("SimplygonVisibilitySampler");
	Params.NameMode = FActorSpawnParameters::ESpawnActorNameMode::Requested;
	if (!Actor)
	{
		
		Actor = GWorld->GetWorld()->SpawnActor<ASimplygonVisibilitySamplerVisualizer>(ASimplygonVisibilitySamplerVisualizer::StaticClass(), FTransform::Identity, Params);
		
		FTelemetryBuilder Builder;
		Builder.SetProperties({ FTelemetry::Prop(L"action", L"add_navmesh_visiaulizer")
			});

		FTelemetry::Record(SGTC::Visualizer, SGTC::Editor, MoveTemp(Builder));
	}
	GEditor->SelectNone( true, true );
	GEditor->SelectActor(Actor, true, true);
}

void FSimplygonEditorModule::AddClippingPlanesVisualizer()
{
	ASimplygonClippingPlanesVisualizer* Actor = FindFirstOrDefaultActorOfTypeInLevel<ASimplygonClippingPlanesVisualizer>();
	FActorSpawnParameters Params;
	Params.Name = TEXT("SimplygonClippingPlanes");
	Params.NameMode = FActorSpawnParameters::ESpawnActorNameMode::Requested;
	if (!Actor)
	{
		Actor = GWorld->GetWorld()->SpawnActor<ASimplygonClippingPlanesVisualizer>(ASimplygonClippingPlanesVisualizer::StaticClass(), FTransform::Identity, Params);

		FTelemetryBuilder Builder;
		Builder.SetProperties({ FTelemetry::Prop(L"action", L"add_clipping_plane_visiaulizer")
			});

		FTelemetry::Record(SGTC::Visualizer, SGTC::Editor, MoveTemp(Builder));
	}
	GEditor->SelectNone( true, true );
	GEditor->SelectActor(Actor, true, true);
}

void FSimplygonEditorModule::OnOpenPluginSettings()
{
	if (ISettingsModule* SettingsModule = FModuleManager::GetModulePtr<ISettingsModule>("Settings"))
	{
		SettingsModule->ShowViewer("Project", "Plugins", "Simplygon");
	}
}

void FSimplygonEditorModule::HandleMapChanged(UWorld* InWorld, EMapChangeType InMapChangeType)
{
	ISimplygonPluginMeshReductionModule& SimplygonModule = FModuleManager::GetModuleChecked<ISimplygonPluginMeshReductionModule>("SimplygonPluginMeshReduction");
	
	if (InMapChangeType == EMapChangeType::LoadMap || InMapChangeType == EMapChangeType::NewMap)
	{
		ASimplygonClippingPlanesVisualizer* ClippingPlaneActor = FindFirstOrDefaultActorOfTypeInLevel<ASimplygonClippingPlanesVisualizer>();
		ASimplygonVisibilitySamplerVisualizer* VisibilityActor = FindFirstOrDefaultActorOfTypeInLevel<ASimplygonVisibilitySamplerVisualizer>();

		if(!ClippingPlaneActor)
			SimplygonModule.GetSimplygonReduction()->ClearClippingPlane();
		if (!VisibilityActor)
			SimplygonModule.GetSimplygonReduction()->ClearNavmeshSamples();
		
	}
	
}

#ifdef IMPOSTER_BAKER
void FSimplygonEditorModule::CompletedImposterBake(const FSimplygonJobData& InJobData, Simplygon::spPipeline InPipeline)
{

	FSimplygonImpostorJobData* UserJobData = (FSimplygonImpostorJobData*)InJobData.UserData;

	Simplygon::spScene Scene = InPipeline->GetProcessedScene();
	auto GetGeneratedGeometry = [&Scene]() -> Simplygon::spGeometryData
	{
		if (Scene.IsNull())
			return nullptr;

		Simplygon::spSceneMesh ProxyMesh;
		int32 SelectionSetId = Scene->SelectNodes("ISceneMesh");
		Simplygon::spSelectionSet SelectedMeshNodes =
			Scene->GetSelectionSetTable()->GetSelectionSet(SelectionSetId);
		ProxyMesh = Simplygon::ISceneMesh::SafeCast(
			Scene->GetNodeByGUID(SelectedMeshNodes->GetItem(0)).GetInterface());

		if (ProxyMesh.IsNull())
			return nullptr;

		return ProxyMesh->GetGeometry();
	};

	Simplygon::spGeometryData GeneratedGeometry = GetGeneratedGeometry();
	if (GeneratedGeometry.IsNull())
	{
		UE_LOG(LogSimplygonEditorModule, Error, TEXT("Could not find generated mesh"));
		return;
	}

	//Compute source actor bounds.
	FBox SourceBounds( EForceInit::ForceInit );
	for( const auto Actor : UserJobData->ImpostorGeneratorHelperActor->ActorsToBake )
	{
		SourceBounds += Actor->GetComponentsBoundingBox();
	}

	//use transform to translate back the vertices
	FVector ObjectCenter = SourceBounds.GetCenter(); // Worldspace
	FTransform ObjectCenterTransfrom = FTransform::Identity;
	ObjectCenterTransfrom.SetTranslation( ObjectCenter );
	
	// Convert simplygon geometry data to mesh description
	FMeshDescription OutProxyMesh;
	FStaticMeshAttributes(OutProxyMesh).Register();
	auto CreateMeshDescription = [ &OutProxyMesh, &GeneratedGeometry, &UserJobData, &ObjectCenterTransfrom ]()
	{
		TMap<int32, FName> EmptyMaterialMap;
		FSimplygonDataConverter::CreateMeshDescriptionFromGeometry(GeneratedGeometry, EmptyMaterialMap, OutProxyMesh, true);

		//Reset pivot 	

		// Create a transform from bounds min as to adjust the pivot
		FTransform Transfrom = FTransform::Identity;
		FBox NewMeshBounds = OutProxyMesh.ComputeBoundingBox();
		const FVector Offset = FVector::ZeroVector - NewMeshBounds.GetCenter();
		
		//FBox AdjustedBounds(SourceBounds.Min + Offset, SourceBounds.Max + Offset);
		Transfrom.SetTranslation(Offset);
		//Transfrom.GetMa

		FMeshMergeHelpers::TransformRawMeshVertexData( ObjectCenterTransfrom.Inverse(), OutProxyMesh );

		return true;
	}();

	FString AssetBaseName = FPackageName::GetShortName(UserJobData->AssetName);
	FString AssetBasePath = FPackageName::GetLongPackagePath(UserJobData->BasePackageName) + TEXT("/");

	//AssetBaseName = FPackageName::GetShortName(UserJobData->BasePackageName);
	//AssetBasePath = FPackageName::GetLongPackagePath(UserJobData->BasePackageName) + TEXT("/");

	TArray<UObject*> AssetsToSync;
	UMaterialInstanceConstant* ProxyMaterial = nullptr;

	FFlattenMaterial BakedMaterial;
	FSimplygonDataConverter::CreateFlattenMaterial(Scene, BakedMaterial);

	// Construct proxy static mesh
	UPackage* MeshPackage = nullptr;
	FString PackageName = TEXT("SG_BILLBOARD_") + AssetBaseName;
	FString MeshAssetName = TEXT("SM_") + AssetBaseName;
	if (MeshPackage == nullptr)
	{
		MeshPackage = CreatePackage(NULL, *(AssetBasePath + PackageName));
		MeshPackage->FullyLoad();
		MeshPackage->Modify();
	}
	
	UMaterial* BaseMaterial = LoadObject<UMaterial>(NULL, TEXT("/Simplygon/Materials/M_SG_FlattenBase.M_SG_FlattenBase"), NULL, LOAD_None, NULL);
	FSimplygonFlattenMaterialSettings FlattenMaterialSettings(InPipeline);
	FlattenMaterialSettings.BlendMode = EBlendMode::BLEND_Masked;
	BakedMaterial.BlendMode = EBlendMode::BLEND_Masked;
	ProxyMaterial = ProxyMaterialUtilities::CreateProxyMaterialInstance(
	    MeshPackage, FlattenMaterialSettings, BaseMaterial,  BakedMaterial, AssetBasePath, AssetBaseName, AssetsToSync );

	static const auto AllowStaticLightingVar = IConsoleManager::Get().FindTConsoleVariableDataInt(TEXT("r.AllowStaticLighting"));
	const bool bAllowStaticLighting = (!AllowStaticLightingVar || AllowStaticLightingVar->GetValueOnGameThread() != 0);
	if (bAllowStaticLighting)
	{
		ProxyMaterial->CheckMaterialUsage(MATUSAGE_StaticLighting);
	}

	FStaticMeshComponentRecreateRenderStateContext RecreateRenderStateContext(FindObject<UStaticMesh>(MeshPackage, *MeshAssetName));

	UStaticMesh* StaticMesh = NewObject<UStaticMesh>(MeshPackage, FName(*MeshAssetName), RF_Public | RF_Standalone);
	StaticMesh->InitResources();

	// make sure it has a new lighting guid
	StaticMesh->LightingGuid = FGuid::NewGuid();

	// Set it to use textured lightmaps. Note that Build Lighting will do the error-checking (texcoordindex exists for all LODs, etc).
	StaticMesh->LightMapResolution = 64;//Data->Data->InProxySettings.LightMapResolution;
	StaticMesh->LightMapCoordinateIndex = 1;

	//avoid nav mesh update
	StaticMesh->MarkAsNotHavingNavigationData();

	FStaticMeshSourceModel& SrcModel = StaticMesh->AddSourceModel();
	
	SrcModel.BuildSettings.bRecomputeNormals = false;
	SrcModel.BuildSettings.bRecomputeTangents = false;
	SrcModel.BuildSettings.bRemoveDegenerates = true;
	SrcModel.BuildSettings.bUseHighPrecisionTangentBasis = false;
	SrcModel.BuildSettings.bUseFullPrecisionUVs = false;
	SrcModel.BuildSettings.bGenerateLightmapUVs = false;
	SrcModel.BuildSettings.bBuildReversedIndexBuffer = false;
	SrcModel.BuildSettings.bBuildAdjacencyBuffer = false;
	SrcModel.BuildSettings.DistanceFieldResolutionScale = 0.0f;	//set to not compute distance field by default.

	TPolygonGroupAttributesConstRef<FName> PolygonGroupMaterialSlotName = OutProxyMesh.PolygonGroupAttributes().GetAttributesRef<FName>(MeshAttribute::PolygonGroup::ImportedMaterialSlotName);

	//Commit the FMeshDescription to the source model we just created
	int32 SourceModelIndex = StaticMesh->GetNumSourceModels() - 1;
	FMeshDescription* MeshDescription = StaticMesh->CreateMeshDescription(SourceModelIndex);
	if (ensure(MeshDescription))
	{
		*MeshDescription = OutProxyMesh;

		FStaticMaterial NewMaterial(ProxyMaterial);
		if (MeshDescription->PolygonGroups().Num() > 0)
		{
			NewMaterial.ImportedMaterialSlotName = PolygonGroupMaterialSlotName[MeshDescription->PolygonGroups().GetFirstValidID()];
		}
		StaticMesh->StaticMaterials.Add(NewMaterial);
		
		StaticMesh->CommitMeshDescription(SourceModelIndex);
	}

	//Set the Imported version before calling the build
	StaticMesh->ImportVersion = EImportStaticMeshVersion::LastVersion;

	// setup section info map
	TPolygonGroupAttributesConstRef<FName> PolygonGroupImportedMaterialSlotNames = OutProxyMesh.PolygonGroupAttributes().GetAttributesRef<FName>(MeshAttribute::PolygonGroup::ImportedMaterialSlotName);
	TArray<int32> UniqueMaterialIndices;
	for (const FPolygonGroupID& PolygonGroupID : OutProxyMesh.PolygonGroups().GetElementIDs())
	{
		int32 PolygonGroupMaterialIndex = PolygonGroupID.GetValue();
		FName PolygonGroupName = PolygonGroupImportedMaterialSlotNames[PolygonGroupID];
		if (PolygonGroupName != NAME_None)
		{
			for (int32 MaterialIndex = 0; MaterialIndex < StaticMesh->StaticMaterials.Num(); ++MaterialIndex)
			{
				if (StaticMesh->StaticMaterials[MaterialIndex].ImportedMaterialSlotName == PolygonGroupName)
				{
					PolygonGroupMaterialIndex = MaterialIndex;
					break;
				}
			}
		}
		if (!StaticMesh->StaticMaterials.IsValidIndex(PolygonGroupMaterialIndex))
		{
			PolygonGroupMaterialIndex = 0;
		}
		UniqueMaterialIndices.AddUnique(PolygonGroupMaterialIndex);
	}

	int32 SectionIndex = 0;
	for (int32 UniqueMaterialIndex : UniqueMaterialIndices)
	{
		FMeshSectionInfo MeshSectionInfo(UniqueMaterialIndex);

		// enable/disable section collision according to settings
		MeshSectionInfo.bEnableCollision = true;

		StaticMesh->GetSectionInfoMap().Set(0, SectionIndex, MeshSectionInfo);
		SectionIndex++;
	}

	StaticMesh->Build();
	StaticMesh->PostEditChange();

	UWorld* World = UserJobData->ImpostorGeneratorHelperActor->GetWorld();
	FTransform Transfrom = UserJobData->ImpostorGeneratorHelperActor->GetTransform();
	FQuat Rotation = Transfrom.GetRotation();
	Transfrom = FTransform::Identity;
	//Transfrom.SetRotation(Rotation);
	bool bDirtyPackage = false;
	FActorSpawnParameters SpawnParams;

	//create a static mesh and apply scaling.
	AStaticMeshActor* NewActor = World->SpawnActor<AStaticMeshActor>(AStaticMeshActor::StaticClass(), Transfrom);
	NewActor->GetStaticMeshComponent()->SetStaticMesh(StaticMesh);
	NewActor->PostEditChange();

	FBox NewBounds = NewActor->GetComponentsBoundingBox();

	
	
	float NewExtent = NewBounds.GetExtent().Size();
	float SourceExtent = SourceBounds.GetExtent().Size();
	float ScaleFactor = SourceBounds.GetSize().SizeSquared() / NewBounds.GetSize().SizeSquared();
	
	FVector ViewDirection = UserJobData->ImpostorGeneratorHelperActor->ImpostorFromSingleViewSettings.ViewDirection;
	
	FTransform ActorTransfrom = FTransform::Identity;
	ActorTransfrom.SetScale3D( FVector( ScaleFactor * ( static_cast<int32>( ViewDirection.X ) ^ 1 ),
	                                    ScaleFactor * ( static_cast<int32>( ViewDirection.Y ) ^ 1 ),
	                                    ScaleFactor * ( static_cast<int32>( ViewDirection.Z ) ^ 1 ) ) );
	//ActorTransfrom->S
	ActorTransfrom.Accumulate( ObjectCenterTransfrom );
	NewActor->SetActorTransform( ActorTransfrom );
	
	//Note: update pipeline has after standin is generated. 
	TArray<UTexture2D*> BakedTextureList;
	for (auto& Asset : AssetsToSync)
	{
		UTexture2D* Texture = Cast<UTexture2D>(Asset);
		if (Texture)
			BakedTextureList.Add(Texture);
	}

	CollectGarbage(GARBAGE_COLLECTION_KEEPFLAGS);
	
}

void FSimplygonEditorModule::BakeImpostors(AImpostorGeneratorHelperActor* InActor)
{

	Simplygon::ISimplygon* SimplygonAPI = ISimplygonModule::Get().GetSDK(true);
	if (SimplygonAPI == nullptr)
		return;


	TArray<AImpostorGeneratorHelperActor*> ImposterGeneratorHelpers;

	if (!InActor)
	{
		for (auto Level : GWorld->GetLevels())
		{
			if (Level->IsValidLowLevelFast() && Level->bIsVisible)
			{

				for (auto ActorIt = Level->Actors.CreateIterator(); ActorIt; ++ActorIt)
				{
					AActor* Actor = *ActorIt;
					if (Actor->IsValidLowLevelFast() && Actor->IsA(AImpostorGeneratorHelperActor::StaticClass()))
					{
						AImpostorGeneratorHelperActor* ImposterHelper = CastChecked<AImpostorGeneratorHelperActor>(Actor);
						if (ImposterHelper && ImposterHelper->ActorsToBake.Num() > 0 && ImposterHelper->IsValidPipeline())
						{

							ImposterGeneratorHelpers.Add(ImposterHelper);
						}
							
					}
				}
			}
		}
	}
	else
	{
		ImposterGeneratorHelpers.Add(InActor);
	}

	if (ImposterGeneratorHelpers.Num() == 0)
	{
		UE_LOG(LogSimplygonEditorModule, Warning, TEXT("No valid impoters to generate. Either ImpostersHelpers have no actors or the pipelien is invalid."));
		return;
	}
	

	ISimplygonPluginUtilitiesModule& SimplygonUtilities = FModuleManager::Get().LoadModuleChecked<ISimplygonPluginUtilitiesModule>("SimplygonPluginUtilities");
	ISimplygonPluginMeshReductionModule& ReductionModule = FModuleManager::LoadModuleChecked<ISimplygonPluginMeshReductionModule>("SimplygonPluginMeshReduction");
	ISimplygonReduction* SimplygonProcessingInterface = ReductionModule.GetSimplygonReduction();
	
	ISimplygonPluginUtilities& PluginUtils = SimplygonUtilities.GetUtilities();
	FSimplygonProcessor* SimplygonProcessor = PluginUtils.GetSimplygonProcessor();

	// Progress
	int32 SimplygonAmountOfWork = ImposterGeneratorHelpers.Num();
	volatile int32 SimplygonWorkCompleted = 0;
	volatile int32 SimplygonWorkProgress = 0;

	FText DataConvertionProgressText = LOCTEXT("GenerateImposters_ConvertData", "Converting mesh data ({0}/{1})");
	int32 DataConvertionWorkComplete = 0;
	float DataConvertionAmountOfWork = (float)ImposterGeneratorHelpers.Num() * 0.2f;

	FText InitialProgressText = LOCTEXT("GenerateImposters_Initial", "Preparing to generate imposter meshes...");
	FScopedSlowTask SlowTask(SimplygonAmountOfWork + DataConvertionAmountOfWork, InitialProgressText);
	SlowTask.MakeDialog();

	FSimplygonJobDelegates JobDelegates;
	JobDelegates.Progress.BindLambda([&SimplygonWorkProgress](const FSimplygonJobData& InJobData, int32 ProgressPercentage, int32 ProgressPercentageDelta)
		{
			FPlatformAtomics::InterlockedAdd(&SimplygonWorkProgress, ProgressPercentageDelta);
		});

	JobDelegates.Complete.BindLambda([&SimplygonWorkCompleted, &SimplygonProcessor, this](const FSimplygonJobData& InJobData, const Simplygon::spPipeline& Pipeline)
		{
			FSimplygonProcessGameThreadDelegate CompleteJobOnGameThread;
			CompleteJobOnGameThread.BindLambda([=]()
				{
					CompletedImposterBake(InJobData, Pipeline);
					FSimplygonImpostorJobData* UserJobData = (FSimplygonImpostorJobData*)InJobData.UserData;
					delete UserJobData;
				});
			
			SimplygonProcessor->Add(CompleteJobOnGameThread);

			FTelemetry::Record(TEXT("JobCompleted"), SGTC::Standin);
			UE_LOG(LogSimplygonEditorModule, Log, TEXT("StandinJobCompleted"));
			FPlatformAtomics::InterlockedAdd(&SimplygonWorkCompleted, 1);
		});

	JobDelegates.Failed.BindLambda([&SimplygonWorkCompleted](const FSimplygonJobData& InJobData, const FString& InError)
		{
			FPlatformAtomics::InterlockedAdd(&SimplygonWorkCompleted, 1);
			FSimplygonImpostorJobData* ImpostorJobData = (FSimplygonImpostorJobData*)InJobData.UserData;
			delete ImpostorJobData;

			FTelemetryBuilder Builder;
			Builder.SetProperties({
				FTelemetry::Prop(TEXT("error_message"),InError)
				});

			UE_LOG(LogSimplygonEditorModule, Error, TEXT("%s"), *FString::Printf(TEXT("ImpostorJobFailed : %s"), *InError));
			FMessageDialog::Open(EAppMsgType::Ok, FText::FromString(InError));

			FTelemetry::Record(TEXT("JobFailed"), SGTC::Editor, MoveTemp(Builder));
		});

	
	FTelemetryBuilder Builder;
	Builder.SetProperties({
		FTelemetry::Prop(TEXT("num_meshes"),ImposterGeneratorHelpers.Num())
		});

	FTelemetry::Record(TEXT("GeneterateBillboardImposterMeshes"), SGTC::Standin, MoveTemp(Builder));
	
	for (auto Actor : ImposterGeneratorHelpers)
	{
		Actor->Update();

		FSimplygonImpostorJobData* JobUserData = new FSimplygonImpostorJobData();
		JobUserData->ImpostorGeneratorHelperActor = Actor;
		JobUserData->AssetName = Actor->GetName();
		JobUserData->BasePackageName = Actor->PackagePath;

		FSimplygonJobData JobData;
		JobData.JobGuid = FGuid::NewGuid();
		JobData.UserData = JobUserData;

		FString ErrorMsg;
		if (!Actor->ImpostorPipeline->HasValidSettings(ErrorMsg))
		{
			ErrorMsg = "Impostor for actor " + Actor->GetActorNameOrLabel() + " won't be generated." + ErrorMsg;
			JobDelegates.Failed.ExecuteIfBound(JobData, ErrorMsg);
			continue;	
		}

		Simplygon::spPipeline Pipeline = Actor->ImpostorPipeline->CreatePipeline(SimplygonAPI);
		FSimplygonFlattenMaterialSettings FlattenMaterialSettings(Pipeline);
		FSimplygonMeshMaterialInfo MeshMaterialInfo(true);
		Simplygon::spScene SceneToProcess = SimplygonAPI->CreateScene();

		for (AActor* ActorToProcess : Actor->ActorsToBake)
		{
			if (!ActorToProcess)
				continue;

			TInlineComponentArray<UStaticMeshComponent*> Components;
			ActorToProcess->GetComponents<UStaticMeshComponent>(Components, true);

			// if the actor is a custom actor with attached static mesh components i.e (Prefabricator)
			TArray<AActor*> AttachedActors;
			ActorToProcess->GetAttachedActors(AttachedActors);

			for (AActor* AttachedActor : AttachedActors)
			{
				AttachedActor->GetComponents<UStaticMeshComponent>(Components, true);
			}

			for (UStaticMeshComponent* Component : Components)
			{
				FSimplygonDataConverter::AddToScene(SimplygonAPI, SceneToProcess, FlattenMaterialSettings, Component, MeshMaterialInfo);
			}

			FSimplygonDataConverter::AddToScene(SimplygonAPI, SceneToProcess, MeshMaterialInfo);

		}

		

		// 5. Execute Simplygon using the scene we just built
		SimplygonProcessingInterface->RunSceneAsync(SceneToProcess, Pipeline, JobData, JobDelegates.Progress, JobDelegates.Complete, JobDelegates.Failed);

		++DataConvertionWorkComplete;
	}

	//Wait for complete
	FText SimplygonProgressText = LOCTEXT("SimplygonEditorUtilities_GenerateImposter", "Generating imposter mesh ({0}/{1})");
	int32 LastWorkProgress = 0;
	while (SimplygonWorkCompleted < SimplygonAmountOfWork)
	{
		if (SimplygonWorkProgress > LastWorkProgress)
		{
			int32 CurrentProgress = SimplygonWorkProgress;
			int32 Delta = CurrentProgress - LastWorkProgress;
			SlowTask.EnterProgressFrame((float)Delta / 100.0f, FText::Format(SimplygonProgressText, FText::AsNumber(SimplygonWorkCompleted + 1), FText::AsNumber(SimplygonAmountOfWork)));
			LastWorkProgress = CurrentProgress;
		}
		SimplygonProcessor->Tick(0.0f);
		FPlatformProcess::Sleep(0.1f);
	}
}
#endif

#undef LOCTEXT_NAMESPACE
